﻿using System.ComponentModel;
using System.Reflection;

using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;

using Swashbuckle.AspNetCore.SwaggerGen;

namespace JackProjectTemplate.Web.Filters
{
    public class SwaggerDocumentFilter : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            ArgumentNullException.ThrowIfNull(context);
            ArgumentNullException.ThrowIfNull(swaggerDoc);

            //IList<OpenApiTag> tags = swaggerDoc.Tags;
            //List<OpenApiTag> tags2 = (from t in swaggerDoc.Paths.SelectMany((KeyValuePair<string, OpenApiPathItem> p) => p.Value.Operations).SelectMany((KeyValuePair<OperationType, OpenApiOperation> o) => o.Value.Tags).Concat(swaggerDoc.Tags)
            //                          group t by t.Name into g
            //                          select g.Last() into t
            //                          select new OpenApiTag
            //                          {
            //                              Name = t.Name,
            //                              Description = t.Description
            //                          }).ToList();
            //swaggerDoc.Tags = tags2;

            foreach (var property in swaggerDoc.Components.Schemas.Where(x => x.Value?.Enum?.Count > 0))
            {
                IList<IOpenApiAny> propertyEnums = property.Value.Enum;
                if (propertyEnums != null && propertyEnums.Count > 0)
                {
                    property.Value.Description += DescribeEnum(propertyEnums, property.Key);
                }
            }

            foreach (var pathItem in swaggerDoc.Paths.Values)
            {
                DescribeEnumParameters(pathItem.Operations, swaggerDoc);
            }
        }

        private void DescribeEnumParameters(IDictionary<OperationType, OpenApiOperation> operations, OpenApiDocument swaggerDoc)
        {
            if (operations != null)
            {
                foreach (var oper in operations)
                {
                    foreach (var param in oper.Value.Parameters)
                    {
                        var paramEnum = swaggerDoc.Components.Schemas.FirstOrDefault(x => x.Key == param.Name);
                        if (paramEnum.Value != null)
                        {
                            param.Description += DescribeEnum(paramEnum.Value.Enum, paramEnum.Key);
                        }
                    }
                }
            }
        }

        private static Type? GetEnumTypeByName(string enumTypeName)
        {
            return AppDomain.CurrentDomain
                .GetAssemblies()
                .SelectMany(x => x.GetTypes())
                .FirstOrDefault(x => x.Name == enumTypeName || x.FullName == enumTypeName);
        }

        private string DescribeEnum(IList<IOpenApiAny> enums, string proprtyTypeName)
        {
            List<string> enumDescriptions = new();
            var enumType = GetEnumTypeByName(proprtyTypeName);
            if (enumType == null)
                return string.Empty;

            foreach (OpenApiInteger enumOption in enums.Cast<OpenApiInteger>())
            {
                int enumInt = enumOption.Value;
                var name = System.Enum.GetName(enumType, enumInt);
                if (name == null)
                {
                    enumDescriptions.Add($"{enumInt}");
                    continue;
                }
                FieldInfo? field = enumType.GetField(name);
                if (field == null)
                {
                    enumDescriptions.Add($"{enumInt} = {name}");
                    continue;
                }

                DescriptionAttribute? attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attribute == null)
                {
                    enumDescriptions.Add($"{enumInt} = {name}");
                    continue;
                }

                enumDescriptions.Add($"{enumInt} = {name}({attribute.Description})");
            }

            return string.Join(", ", [.. enumDescriptions]);
        }
    }
}
